#!@BASH_PATH@
#
# ocf:pacemaker:SysInfo resource agent
#
# Original copyright 2004 SUSE LINUX AG, Lars Marowsky-Br<E9>e
# Later changes copyright 2008-2019 the Pacemaker project contributors
#
# The version control history for this file may have further details.
#
# This source code is licensed under the GNU General Public License version 2
# (GPLv2) WITHOUT ANY WARRANTY.
#

#
# This agent records (in the CIB) various attributes of a node
#
#######################################################################
# Initialization:

: ${OCF_FUNCTIONS:="${OCF_ROOT}/resource.d/heartbeat/.ocf-shellfuncs"}
. "${OCF_FUNCTIONS}"
: ${__OCF_ACTION:="$1"}

#######################################################################

meta_data() {
    cat <<END
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="SysInfo" version="1.0">
<version>1.0</version>

<longdesc lang="en">
This is a SysInfo Resource Agent.
It records (in the CIB) various attributes of a node
Sample Linux output:
   arch:   i686
   os:     Linux-2.4.26-gentoo-r14
   free_swap:      1999
   cpu_info:       Intel(R) Celeron(R) CPU 2.40GHz
   cpu_speed:      4771.02
   cpu_cores:      1
   cpu_load:       0.00
   ram_total:      513
   ram_free:       117
   root_free:      2.4
   #health_disk:   red

Sample Darwin output:
   arch:   i386
   os:     Darwin-8.6.2
   cpu_info:       Intel Core Duo
   cpu_speed:      2.16
   cpu_cores:      2
   cpu_load:       0.18
   ram_total:      2016
   ram_free:       787
   root_free:      13
   #health_disk:   green

Units:
   free_swap: MB
   ram_*:     MB
   cpu_speed (Linux): bogomips
   cpu_speed (Darwin): GHz
   *_free:    GB (or user-defined: disk_unit)

</longdesc>
<shortdesc lang="en">SysInfo resource agent</shortdesc>

<parameters>

<parameter name="pidfile" unique="1">
<longdesc lang="en">PID file</longdesc>
<shortdesc lang="en">PID file</shortdesc>
<content type="string" default="$OCF_RESKEY_pidfile" />
</parameter>

<parameter name="delay" unique="0">
<longdesc lang="en">Interval to allow values to stabilize</longdesc>
<shortdesc lang="en">Dampening Delay</shortdesc>
<content type="string" default="0s" />
</parameter>

<parameter name="disks" unique="0">
<longdesc lang="en">
Filesystems or Paths to be queried for free disk space as a SPACE
separated list - e.g "/dev/sda1 /tmp".
Results will be written to an attribute with leading slashes
removed, and other slashes replaced with underscore, and the word
'free' appended - e.g for /dev/sda1 it would be 'dev_sda1_free'.
Note: The root filesystem '/' is always queried to an attribute
named 'root_free'
</longdesc>
<shortdesc lang="en">List of Filesytems/Paths to query for free disk space</shortdesc>
<content type="string" />
</parameter>

<parameter name="disk_unit" unique="0">
<longdesc lang="en">
Unit to report disk free space in.
Can be one of: B, K, M, G, T, P (case-insensitive)
</longdesc>
<shortdesc lang="en">Unit to report disk free space in</shortdesc>
<content type="string" default="G"/>
</parameter>

<parameter name="min_disk_free" unique="0">
<longdesc lang="en">
The amount of free space required in monitored disks. If any
of the monitored disks has less than this amount of free space,
, with the node attribute "#health_disk" changing to "red",
all resources will move away from the node. Set the node-health-strategy
property appropriately for this to take effect.
If the unit is not specified, it defaults to disk_unit.
</longdesc>
<shortdesc lang="en">minimum disk free space required</shortdesc>
<content type="string" default=""/>
</parameter>


</parameters>
<actions>
<action name="start"   timeout="20s" />
<action name="stop"    timeout="20s" />
<action name="monitor" timeout="20s" interval="60s"/>
<action name="meta-data"  timeout="5s" />
<action name="validate-all"  timeout="30s" />
</actions>
</resource-agent>
END
}

#######################################################################

UpdateStat() {
    name="$1"; shift
    value="$*"
    printf "%s:\t%s\n" "$name" "$value"
    if [ "$__OCF_ACTION" = "start" ] ; then
        "${HA_SBIN_DIR}/attrd_updater" ${OCF_RESKEY_delay} -S status -n $name -B "$value"
    else
        "${HA_SBIN_DIR}/attrd_updater" ${OCF_RESKEY_delay} -S status -n $name -v "$value"
    fi
}

SysInfoStats() {
    local DISK_STATUS="green"

    UpdateStat arch "$(uname -m)"
    UpdateStat os "$(uname -s)-$(uname -r)"

    case $(uname -s) in
        "Darwin")
            mem=$(top -l 1 | grep Mem: | awk '{print $10}')
            mem_used=$(top -l 1 | grep Mem: | awk '{print $8}')
            mem=$(SysInfo_mem_units "$mem")
            mem_used=$(SysInfo_mem_units "$mem_used")
            mem_total=$(expr $mem_used + $mem)
            cpu_type=$(system_profiler SPHardwareDataType | awk -F': ' '/^CPU Type/ {print $2; exit}')
            cpu_speed=$(system_profiler SPHardwareDataType | awk -F': ' '/^CPU Speed/ {print $2; exit}')
            cpu_cores=$(system_profiler SPHardwareDataType | awk -F': ' '/^Number Of/ {print $2; exit}')
            cpu_load=$(uptime | awk '{ print $10 }')
        ;;
        "FreeBSD")
            cpu_type=$(sysctl -in hw.model)
            cpu_speed=$(sysctl -in dev.cpu.0.freq)
            cpu_cores=$(sysctl -in hw.ncpu)
            cpu_load=$(sysctl -in vm.loadavg | awk '{ print $4 }')

            free_pages=$(sysctl -in vm.stats.vm.v_free_count)
            page_count=$(sysctl -in vm.stats.vm.v_page_count)
            page_size=$(sysctl -in vm.stats.vm.v_page_size)

            mem=$(expr $free_pages \* $page_size / 1024 / 1024)M
            mem_total=$(expr $page_count \* $page_size / 1024 / 1024)M
        ;;
        "Linux")
            if [ -f /proc/cpuinfo ]; then
                cpu_type=$(awk -F': ' '/model name/ {print $2; exit}' /proc/cpuinfo)
                cpu_speed=$(awk -F': ' '/bogomips/ {print $2; exit}' /proc/cpuinfo)
                cpu_cores=$(grep "^processor" /proc/cpuinfo | wc -l)
            fi
            cpu_load=$(uptime | awk '{ print $10 }')

            if [ -f /proc/meminfo ]; then
                # meminfo results are in kB
                mem=$(grep "SwapFree" /proc/meminfo | awk '{print $2"k"}')
                if [ -n "$mem" ]; then
                    UpdateStat free_swap "$(SysInfo_mem_units "$mem")"
                fi
                mem=$(grep "Inactive" /proc/meminfo | awk '{print $2"k"}')
                mem_total=$(grep "MemTotal" /proc/meminfo | awk '{print $2"k"}')
            else
                mem=$(top -n 1 | grep Mem: | awk '{print $7}')
            fi
            ;;
        *)
    esac

    if [ -n "$cpu_type" ]; then
        UpdateStat cpu_info "$cpu_type"
    fi

    if [ -n "$cpu_speed" ]; then
        UpdateStat cpu_speed "$cpu_speed"
    fi

    if [ -n "$cpu_cores" ]; then
        UpdateStat cpu_cores "$cpu_cores"
    fi

    if [ -n "$cpu_load" ]; then
        UpdateStat cpu_load "$cpu_load"
    fi

    if [ -n "$mem" ]; then
        # Massage the memory values
        UpdateStat ram_total "$(SysInfo_mem_units "$mem_total")"
        UpdateStat ram_free "$(SysInfo_mem_units "$mem")"
    fi

    # Portability notes:
    #   o tail: explicit "-n" not available in Solaris; instead simplify
    #     'tail -n <c>' to the equivalent 'tail -<c>'.
    for disk in "/" ${OCF_RESKEY_disks}; do
        unset disk_free disk_label
        disk_free=$(df -h "${disk}" | tail -1 | awk '{print $4}')
        if [ -n "$disk_free" ]; then
            disk_label=$(echo $disk | sed -e 's#^/$#root#;s#^/*##;s#/#_#g')
            disk_free=$(SysInfo_hdd_units "$disk_free")
            UpdateStat "${disk_label}_free" $disk_free
            if [ -n "$MIN_FREE" ] && [ $disk_free -le $MIN_FREE ]; then
                DISK_STATUS="red"
            fi
        fi
    done
    UpdateStat "#health_disk" "$DISK_STATUS"
}

SysInfo_megabytes() {
    # Size in megabytes
    echo $1 | awk '{ n = $0;
        sub( /[0-9]+(.[0-9]+)?/, "" );
        if ( $0 == "" ) { $0 = "G" }; # Do not change previous behavior `if ($0 == "G" || $0 == "") { n *= 1024 };`
        split( n, a, $0 );
        n = a[1];
        if ( /^[pP]i?[bB]?/ ) { n *= 1024 * 1024 * 1024 };
        if ( /^[tT]i?[bB]?/ ) { n *= 1024 * 1024 };
        if ( /^[gG]i?[bB]?/ ) { n *= 1024 };
        if ( /^[mM]i?[bB]?/ ) { n *= 1 };
        if ( /^[kK]i?[bB]?/ ) { n /= 1024 };
        if ( /^[bB]i?/ )      { n /= 1024 * 1024 };
        printf "%d\n", n }' # Intentionally round to an integer
}

SysInfo_mem_units() {
    mem="$1"

    if [ -z "$1" ]; then
        return
    fi

    mem=$(SysInfo_megabytes "$1")
    # Round to the next multiple of 50
    r=$(($mem % 50))
    if [ $r -ne 0 ]; then
        mem=$(($mem + 50 - $r))
    fi

    echo $mem
}

SysInfo_hdd_units() {
    # Defauts to size in gigabytes

    case "$OCF_RESKEY_disk_unit" in
        [Pp]) echo $(($(SysInfo_megabytes "$1") / 1024 / 1024 / 1024));;
        [Tt]) echo $(($(SysInfo_megabytes "$1") / 1024 / 1024));;
        [Gg]) echo $(($(SysInfo_megabytes "$1") / 1024));;
        [Mm]) echo "$(SysInfo_megabytes "$1")" ;;
        [Kk]) echo $(($(SysInfo_megabytes "$1") * 1024));;
        [Bb]) echo $(($(SysInfo_megabytes "$1") * 1024 * 1024));;
        *)
            ocf_log err "Invalid value for disk_unit: $OCF_RESKEY_disk_unit"
            echo $(($(SysInfo_megabytes "$1") / 1024));;
    esac
}

SysInfo_usage() {
    cat <<END
usage: $0 {start|stop|monitor|validate-all|meta-data}

Expects to have a fully populated OCF RA-compliant environment set.
END
}

SysInfo_start() {
    echo $OCF_RESKEY_clone > "$OCF_RESKEY_pidfile"
    SysInfoStats
    exit $OCF_SUCCESS
}

SysInfo_stop() {
    rm "$OCF_RESKEY_pidfile"
    exit $OCF_SUCCESS
}

SysInfo_monitor() {
    if [ -f "$OCF_RESKEY_pidfile" ]; then
        clone=$(cat "$OCF_RESKEY_pidfile")
    fi

    if [ -z "$clone" ]; then
        rm "$OCF_RESKEY_pidfile"
        exit $OCF_NOT_RUNNING

    elif [ "$clone" = "$OCF_RESKEY_clone" ]; then
        SysInfoStats
        exit $OCF_SUCCESS

    elif ocf_is_true "$OCF_RESKEY_CRM_meta_globally_unique"; then
        SysInfoStats
        exit $OCF_SUCCESS
    fi
    exit $OCF_NOT_RUNNING
}

SysInfo_validate() {
    return $OCF_SUCCESS
}

if [ $# -ne 1 ]; then
    SysInfo_usage
    exit $OCF_ERR_ARGS
fi

: ${OCF_RESKEY_pidfile:="${HA_VARRUN%%/}/SysInfo-${OCF_RESOURCE_INSTANCE}"}
: ${OCF_RESKEY_disk_unit:="G"}
: ${OCF_RESKEY_clone:="0"}
if [ -n "${OCF_RESKEY_delay}" ]; then
    OCF_RESKEY_delay="-d ${OCF_RESKEY_delay}"
else
    OCF_RESKEY_delay="-d 0"
fi
MIN_FREE=""
if [ -n "$OCF_RESKEY_min_disk_free" ]; then
    ocf_is_decimal "$OCF_RESKEY_min_disk_free" &&
        OCF_RESKEY_min_disk_free="$OCF_RESKEY_min_disk_free$OCF_RESKEY_disk_unit"
    MIN_FREE=$(SysInfo_hdd_units $OCF_RESKEY_min_disk_free)
fi

case "$__OCF_ACTION" in
meta-data)      meta_data
                exit $OCF_SUCCESS
                ;;
start)          SysInfo_start
                ;;
stop)           SysInfo_stop
                ;;
monitor)        SysInfo_monitor
                ;;
validate-all)   SysInfo_validate
                ;;
usage|help)     SysInfo_usage
                exit $OCF_SUCCESS
                ;;
*)              SysInfo_usage
                exit $OCF_ERR_UNIMPLEMENTED
                ;;
esac

exit $?

# vim: set filetype=sh expandtab tabstop=4 softtabstop=4 shiftwidth=4 textwidth=80:
